#include "tokenizer.h"
#include "errors.h"
#include <iostream>
#include <algorithm>
#include <string>
#include <sstream>
#include <cctype>
#include <vector>
#include <list>
#include <cstdlib>
#include <map>
#include <limits>

namespace CCompiler
{
    bool FiniteAutomation::init;
    FAS FiniteAutomation::Table[fasEnd][256];

    typedef Token (* TokenMakeFunc)(FiniteAutomation &);
    TokenMakeFunc TokenMakers[fasEnd];

	std::map<std::string, TokenType> keywords;
	std::map<FAS, TokenType> DirectStateMap;
    std::map<FAS, std::string> ErrorsTable;

	char TerminalsToChar[256];
	
    std::ostream &operator<<(std::ostream &stream, const Token &token)
    {
        stream << '[' << token.line_ << ',' << token.pos_ << ']';
        stream << " Type: " << TokenTypeStrAnalogs[token.type_];
        stream << " T: " << token.text_;
        if(token.type_ == ttInteger)
            stream << " I: " << token.value_.i_;
        else if(token.type_ == ttChar)
            stream << " C: " << token.value_.c_ << " (" << token.value_.i_ << ')';
        else if(token.type_ == ttReal)
            stream << " F: " << token.value_.f_;
        else if(token.type_ == ttString || token.type_ == ttError)
            stream << " S: " << token.strvalue_;
        return stream << std::endl;
    }

	int OctStrToInt(const std::string &str)
	{
		std::istringstream conv(str);
		int a;
		conv >> std::oct >> a;
		return a;
	}

	int HexStrToInt(const std::string &str)
	{
		std::istringstream conv(str);
		int a;
		conv >> std::hex >> a;
		return a;
	}

    /* Token Makers - pointers to this functions placed in the TokenMakers array */
    Token Ignore(FiniteAutomation &) // never called, only indicates to skip token
    {
        return Token(ttNotToken, "", 0,0);
    }

    Token SimpleMaker(FiniteAutomation &fa)
    {
		return Token(DirectStateMap[fa.state()], fa.processed_, fa.line(), fa.pos());
    }

    Token MakeDec(FiniteAutomation &fa)
    {
        std::istringstream conv(fa.processed_);
        int a;
        conv >> a;
		return Token(ttInteger, fa.processed_, fa.line(), fa.pos(), a);
    }

    Token MakeHex(FiniteAutomation &fa)
    {
        return Token(ttInteger, fa.processed_, fa.line(), fa.pos(), HexStrToInt(fa.processed_));
    }

    Token MakeOct(FiniteAutomation &fa)
    {
        return Token(ttInteger, fa.processed_, fa.line(), fa.pos(), OctStrToInt(fa.processed_));
    }

    Token MakeFloat(FiniteAutomation &fa)
    {
        return Token(ttReal, fa.processed_, fa.line(), fa.pos(), (float)std::atof(fa.processed_.c_str()));
    }

    Token MakeChar(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), fa.processed_[1]);
    }

    Token MakeCharOct(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), OctStrToInt(fa.processed_.substr(2, fa.processed_.length() - 3)));
    }

    Token MakeCharHex(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), HexStrToInt(fa.processed_.substr(3, fa.processed_.length() - 4)));
    }

    Token MakeCharWithSimpleTerminal(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), TerminalsToChar[fa.processed_[2]]);
    }

    Token MakeString(FiniteAutomation &fa)
    {
		std::string strval;
		size_t fixed = 1, i = 1;
		for(; i < fa.processed_.length() - 1; ++i)
		{
			if(fa.processed_[i] != '\\') continue;
			strval += fa.processed_.substr(fixed, i-fixed);
			// now we got a slash!
			++i;
			if(SetOfChars('0', '7').in(fa.processed_[i]))
			{ // got an oct
				int oct_start = i;
				SetOfChars legal_oct_digits('0', '7');
				while(legal_oct_digits.in(fa.processed_[i])) ++i;
				strval += (unsigned char)(OctStrToInt(fa.processed_.substr(oct_start, i - oct_start)));
				fixed = i;
				i--;
				continue;
			}
			else if(SetOfChars('x')('X').in(fa.processed_[i]))
			{ // got a hex
				++i;
				SetOfChars legal_hex_digits = SetOfChars ('0', '9')('A', 'F')('a', 'f');
				if(legal_hex_digits.in(fa.processed_[i]))
				{
					int hex_start = i;
					while(legal_hex_digits.in(fa.processed_[i])) ++i;
					strval += (unsigned char)(HexStrToInt(fa.processed_.substr(hex_start, i - hex_start)));
					fixed = i;
					i--;
					continue;
				}
			}
			strval += TerminalsToChar[fa.processed_[i]];
			fixed = i+1;
		}
		strval += fa.processed_.substr(fixed, i - fixed - (i == fa.processed_.length() ? 1: 0));
        return Token(ttString, fa.processed_, fa.line(), fa.pos(), strval);
    }

    Token MakeID(FiniteAutomation &fa)
    {
		std::map<std::string, TokenType>::iterator i;
        if((i = keywords.find(fa.processed_)) != keywords.end())
            return Token(i->second, fa.processed_, fa.line(), fa.pos());
        else
            return Token(ttIdentificator, fa.processed_, fa.line(), fa.pos());
    }

    /* */

    Tokenizer::Tokenizer(std::istream &stream)
        : stream_(stream), current_(0), fa(stream_)
    {
		if(!stream_) throw Errors::BrokenStream();
		NextToken();
    }

    SetOfChars::Range SetOfChars::MakeNormal(char ch1, char ch2)
    {
        return ch1 > ch2 ? std::make_pair(ch2, ch1): std::make_pair(ch1, ch2);
    }

    SetOfChars &SetOfChars::operator()(char ch)
    {
        singles_.push_back(ch);
        return *this;
    }
    SetOfChars &SetOfChars::operator()(char ch1, char ch2)
    {
        ranges_.push_back(SetOfChars::MakeNormal(ch1, ch2));
        return *this;
    }
    bool SetOfChars::in(char ch)
    {
        for(std::list<Range>::iterator i = ranges_.begin(); i != ranges_.end(); ++i)
            if(i->first <= ch && i->second >= ch) return true;
        for(std::list<char>::iterator i = singles_.begin(); i != singles_.end(); ++i)
            if(*i == ch) return true;
        return false;
    }

    void FiniteAutomation::fillRow(FAS Row, FAS newval, SetOfChars columns)
    {
        for(std::list<char>::iterator i = columns.singles_.begin(); i != columns.singles_.end(); ++i)
            Table[Row][(int)*i] = newval;
        for(std::list<SetOfChars::Range>::iterator i = columns.ranges_.begin(); i != columns.ranges_.end(); ++i)
        {
            for(int a = i->first; a <= i->second; ++a)
                Table[Row][a] = newval;
        }
    }

    void FiniteAutomation::Initialize()
    {
        typedef SetOfChars SOC;
        const char cmax = std::numeric_limits<char>::max(), cmin = std::numeric_limits<char>::min();

        fillRow(fasStart, fasId, SOC('A', 'Z')('a', 'z')('$')('_'));
        fillRow(fasId, fasId, SOC('A', 'Z')('a', 'z')('$')('_')('0', '9'));
        // Zero
        fillRow(fasStart, fasZero, SOC('0'));
        // Oct
        fillRow(fasZero, fasOct, SOC('0','7'));
        fillRow(fasOct, fasOct, SOC('0','7'));
        // Hex
        fillRow(fasZero, fasHexPrefix, SOC('x')('X'));
        fillRow(fasHexPrefix, fasHex, SOC('0','9')('A','F')('a','f'));
        fillRow(fasHex, fasHex, SOC('0','9')('A','F')('a','f'));
        // Dec
        fillRow(fasStart, fasDec, SOC('1','9'));
        fillRow(fasDec, fasDec, SOC('0', '9'));
        fillRow(fasStart, fasDot, SOC('.'));
        // Ellipsis and Point
        fillRow(fasDot, fasDotDot, SOC('.'));
        fillRow(fasDotDot, fasEllipsis, SOC('.'));
        // float
        fillRow(fasDec, fasDecwP, SOC('.'));
        fillRow(fasZero, fasDecwP, SOC('.'));
        fillRow(fasDecwP, fasFloat, SOC('0', '9'));
        fillRow(fasDot, fasFloat, SOC('0', '9'));
        fillRow(fasFloat, fasFloat, SOC('0', '9'));
        // Float with Exponent
        fillRow(fasDec, fasFwE, SOC('E')('e'));
        fillRow(fasFloat, fasFwE, SOC('E')('e'));
        fillRow(fasFwE, fasFwES, SOC('+')('-'));
        fillRow(fasFwES, fasFloatwExp, SOC('0', '9'));
        fillRow(fasFloatwExp, fasFloatwExp, SOC('0', '9'));
        fillRow(fasFwE, fasFloatwExp, SOC('0', '9'));
        // Char
		// To simplify my life I've separate chars into 4 categories
		// Char itself, char with simple escape-symbol and chars with Oct or Hex escapes
        fillRow(fasStart, fas1Q, SOC('\''));
        fillRow(fas1Q, fas1QandC, SOC(cmin, cmax));
        fillRow(fas1Q, fas1QandSlash, SOC('\\'));
        fillRow(fas1QandSlash, fas1QandSimpleTerminal, SOC(cmin, cmax));
        fillRow(fas1QandSlash, fasError, SOC('\n'));
        fillRow(fas1QandSlash, fas1QandOct, SOC('0', '7'));
        fillRow(fas1QandOct, fas1QandOct, SOC('0', '7'));
        fillRow(fas1QandOct, fasCharWithOct, SOC('\''));
        fillRow(fas1QandSlash, fas1QandHex, SOC('x')('X'));
        fillRow(fas1QandHex, fas1QandHex, SOC('0', '9')('A','F')('a', 'f'));
        fillRow(fas1QandHex, fasCharWithHex, SOC('\''));
        fillRow(fas1QandSimpleTerminal, fasCharWithSimpleTerminal, SOC('\''));
        fillRow(fas1Q, fasError, SOC('\n'));
        fillRow(fas1QandC, fasChar, SOC('\''));
        // String
        fillRow(fasStart, fas2Q, SOC('"'));
        fillRow(fas2Q, fas2QandC, SOC(cmin, cmax));
		fillRow(fas2Q, fasString, SOC('"'));
		fillRow(fas2Q, fas2QandSlash, SOC('\\'));
		fillRow(fas2QandC, fas2QandC, SOC(cmin, cmax));
        fillRow(fas2QandC, fas2QandSlash, SOC('\\'));
		fillRow(fas2QandSlash, fas2QandC, SOC(cmin, cmax));
        fillRow(fas2QandSlash, fasError, SOC('\n'));
        fillRow(fas2Q, fasError, SOC('\n'));
        fillRow(fas2QandC, fasError, SOC('\n'));
        fillRow(fas2QandC, fasString, SOC('"'));

        // Delimeters
		// This section is quite easy
        fillRow(fasStart, fasComma, SOC(','));
        fillRow(fasStart, fasSemicolon, SOC(';'));
        fillRow(fasStart, fasColon, SOC(':'));
        fillRow(fasStart, fasOpenBrkt, SOC('('));
        fillRow(fasStart, fasOpenCBrkt, SOC('{'));
        fillRow(fasStart, fasCloseBrkt, SOC(')'));
        fillRow(fasStart, fasCloseCBrkt, SOC('}'));
        fillRow(fasStart, fasOpenSBrkt, SOC('['));
        fillRow(fasStart, fasCloseSBrkt, SOC(']'));
        // Operations
		// And no problems with this
        fillRow(fasStart, fasAssign, SOC('='));
        fillRow(fasAssign, fasEqual, SOC('='));
        fillRow(fasStart, fasPlus, SOC('+'));
        fillRow(fasStart, fasMinus, SOC('-'));
        fillRow(fasStart, fasBAnd, SOC('&'));
        fillRow(fasStart, fasBOr, SOC('|'));
        fillRow(fasStart, fasXor, SOC('^'));
        fillRow(fasBAnd, fasAnd, SOC('&'));
        fillRow(fasBOr, fasOr, SOC('|'));
        fillRow(fasStart, fasNot, SOC('!'));
        fillRow(fasPlus, fasPlusAssign, SOC('='));
        fillRow(fasMinus, fasMinusAssign, SOC('='));
        fillRow(fasStart, fasAsterisk, SOC('*'));
        fillRow(fasAsterisk, fasMulAssign, SOC('='));
        fillRow(fasStart, fasDiv, SOC('/'));
        fillRow(fasDiv, fasDivAssign, SOC('='));
        fillRow(fasStart, fasMod, SOC('%'));
        fillRow(fasMod, fasModAssign, SOC('='));
        fillRow(fasPlus, fasInc, SOC('+'));
        fillRow(fasMinus, fasDecr, SOC('-'));
        fillRow(fasBAnd, fasBAndAssign, SOC('='));
        fillRow(fasBOr, fasBOrAssign, SOC('='));
        fillRow(fasXor, fasXorAssign, SOC('='));
        fillRow(fasNot, fasNotEqual, SOC('='));
        fillRow(fasStart, fasGreater, SOC('>'));
        fillRow(fasStart, fasLess, SOC('<'));
        fillRow(fasGreater, fasGreaterEqual, SOC('='));
        fillRow(fasLess, fasLessEqual, SOC('='));
        fillRow(fasMinus, fasArrow, SOC('>'));
        fillRow(fasLess, fasShL, SOC('<'));
        fillRow(fasGreater, fasShR, SOC('>'));
        fillRow(fasStart, fasQuestion, SOC('?'));
        fillRow(fasStart, fasComplement, SOC('~'));
        fillRow(fasShL, fasShLAssign, SOC('='));
        fillRow(fasShR, fasShRAssign, SOC('='));
    // not tokens
	// Indeed, we simply ignoring this states
        fillRow(fasStart, fasWhiteSpace, SOC(' '));
        fillRow(fasStart, fasWhiteSpace, SOC('\t'));
        fillRow(fasStart, fasCRLF, SOC('\n'));
        fillRow(fasDiv, fasLComment, SOC('/'));
        fillRow(fasLComment, fasLComment, SOC(cmin, cmax));
        fillRow(fasLComment, fasError, SOC('\n'));

        fillRow(fasDiv, fasMLCommentOpen, SOC('*'));
        fillRow(fasMLCommentOpen, fasMLCommentBody, SOC(cmin, cmax));
        fillRow(fasMLCommentOpen, fasMLCommentStar, SOC('*'));
        fillRow(fasMLCommentBody, fasMLCommentBody, SOC(cmin, cmax));
        fillRow(fasMLCommentBody, fasMLCommentStar, SOC('*'));
        fillRow(fasMLCommentStar, fasMLCommentBody, SOC(cmin, cmax));
        fillRow(fasMLCommentStar, fasMLComment, SOC('/'));

        // Other Tables
		//This table maps each valid state to specific Token Maker function
        std::fill(TokenMakers + fasDot, TokenMakers + fasWhiteSpace, SimpleMaker);
        TokenMakers[fasId] = MakeID;
        TokenMakers[fasZero] = MakeDec;
        TokenMakers[fasHex] = MakeHex;
        TokenMakers[fasOct] = MakeOct;
        TokenMakers[fasDec] = MakeDec;
        std::fill(TokenMakers + fasWhiteSpace, TokenMakers + fasMLCommentOpen, Ignore);
        TokenMakers[fasChar] = MakeChar;
        TokenMakers[fasCharWithSimpleTerminal] = MakeCharWithSimpleTerminal;
        TokenMakers[fasCharWithHex] = MakeCharHex;
        TokenMakers[fasCharWithOct] = MakeCharOct;
        TokenMakers[fasString] = MakeString;
        TokenMakers[fasFloat] = MakeFloat;
        TokenMakers[fasFloatwExp] = MakeFloat;
        TokenMakers[fasEof] = SimpleMaker;
		// This table maps each simple escape-symbol to appropriate char value
        for(char i = cmin; i < cmax; ++i)
            TerminalsToChar[i] = i;
        TerminalsToChar[cmax] = cmax;
        TerminalsToChar['t'] = '\t';
        TerminalsToChar['n'] = '\n';
        TerminalsToChar['v'] = '\v';
        TerminalsToChar['b'] = '\b';
        TerminalsToChar['r'] = '\r';
        TerminalsToChar['f'] = '\f';
        TerminalsToChar['a'] = '\a';
        TerminalsToChar['\\'] = '\\';
        TerminalsToChar['\?'] = '\?';
        TerminalsToChar['\''] = '\'';
        TerminalsToChar['\"'] = '\"';
    }

    FiniteAutomation::FiniteAutomation(std::basic_istream<char> &stream):
        stream_(stream), line_(1), pos_(1), posInLine_(1), processed_(""),state_(fasStart)
    {
        if(!init)
        {
             FiniteAutomation::Initialize();
             init = true;
        }
        processed_.reserve(255);
    }

    void FiniteAutomation::launch()
    {
        state_ = fasStart;
        ch_ = stream_.get();
        pos_ = posInLine_;
        processed_.erase();
        if(stream_.eof())
            state_ = fasEof;
        else
        {
            while(Table[state_][(int)ch_])
            {
                processed_ += ch_;
                state_ = Table[state_][(int)ch_];
                if(ch_ == '\n')
                {
                    line_++;
                    posInLine_ = 0;
                }
                ch_ = stream_.get();
				if(stream_.eof())
					break;
                posInLine_++;
            }
            stream_.putback(ch_);
        }
    }

    Token Tokenizer::GetCurToken()
    {
        return token_;
    }

    Token Tokenizer::NextToken()
    {
		if(token_.type_ == ttError) return token_; // Prevent to read new tokens
	relaunch: 
		fa.launch();
		if(TokenMakers[fa.state()] == Ignore) goto relaunch;
		else if(TokenMakers[fa.state()] == 0)
		// Yep, Error is a token too
			throw Errors::TokenError(ErrorsTable[fa.state()], fa.line(), fa.pos(), fa.processed_);
			//token_ = Token(ttError, fa.processed_ + fa.last_read_ch(), fa.line(), fa.pos(), ErrorsTable[fa.state()]);
		else
			token_ = TokenMakers[fa.state()](fa);
		return token_;
    }

	class Initializer
	{
		Initializer();
	public:
		static Initializer initializer;
	};

	Initializer Initializer::initializer;

	Initializer::Initializer()
	{
		std::map<std::string, TokenType> &kw = keywords;
		kw["int"] = ttInt; 
		kw["float"] = ttFloat; 
		kw["void"] = ttVoid; 
		kw["return"] = ttReturn;
		kw["struct"] = ttStruct; 
		kw["for"] = ttFor; 
		kw["while"] = ttWhile; 
		kw["if"] = ttIf; 
		kw["switch"] = ttSwitch; 
		kw["case"] = ttCase;
		kw["else"] = ttElse; 
		kw["typedef"] = ttTypedef; 
		kw["auto"] = ttAuto; 
		kw["static"] = ttStatic;
		kw["extern"] = ttExtern; 
		kw["sizeof"] = ttSizeof;

		std::map<FAS, TokenType> &dm = DirectStateMap;
		dm[fasDot] = ttDot;
		dm[fasEllipsis] = ttEllipsis;
		dm[fasComma] = ttComma; 
		dm[fasSemicolon] = ttSemicolon; 
		dm[fasColon] = ttColon;
		dm[fasOpenBrkt] = ttOpenBrkt; 
		dm[fasCloseBrkt] = ttCloseBrkt; 
		dm[fasOpenCBrkt] = ttOpenCBrkt; 
		dm[fasCloseCBrkt] = ttCloseCBrkt;
		dm[fasOpenSBrkt] = ttOpenSBrkt; 
		dm[fasCloseSBrkt] = ttCloseSBrkt; 
		dm[fasAssign] = ttAssign; 
		dm[fasEqual] = ttEqual; 
		dm[fasPlus] = ttPlus; 
		dm[fasMinus] = ttMinus; 
		dm[fasBAnd] = ttBAnd; 
		dm[fasBOr] = ttBOr; 
		dm[fasXor] = ttXor;
		dm[fasAnd] = ttAnd; 
		dm[fasOr] = ttOr; 
		dm[fasNot] = ttNot;
		dm[fasPlusAssign] = ttPlusAssign; 
		dm[fasMinusAssign] = ttMinusAssign;
		dm[fasAsterisk] = ttAsterisk; 
		dm[fasMulAssign] = ttMulAssign; 
		dm[fasDiv] = ttDiv;
		dm[fasDivAssign] = ttDivAssign; 
		dm[fasMod] = ttMod;
		dm[fasModAssign] = ttModAssign;
		dm[fasInc] = ttInc; 
		dm[fasDecr] = ttDecr;
		dm[fasBAndAssign] = ttBAndAssign;
		dm[fasBOrAssign] = ttBOrAssign;
		dm[fasXorAssign] = ttXorAssign;
		dm[fasNotEqual] = ttNotEqual;
		dm[fasGreater] = ttGreater;
		dm[fasLess] = ttLess; 
		dm[fasGreaterEqual] = ttGreaterEqual;
		dm[fasLessEqual] = ttLessEqual;
		dm[fasArrow] = ttArrow;
		dm[fasShL] = ttShL;
		dm[fasShR] = ttShR;
		dm[fasQuestion] = ttQuestion;
		dm[fasComplement] = ttComplement;
		dm[fasShLAssign] = ttShLAssign;
		dm[fasShRAssign] = ttShRAssign;
		dm[fasEof] = ttEof;

		std::map<FAS, std::string> &et = ErrorsTable;

		const char *MLCommentErrorMsg = "Eof meeted before closing the multiline comment";
		const char *CharNotClosedMsg = "Missing closing single quote";
		const char *BadStringMsg = "Bad String";

		et[fasStart] = "Unhandled Lexem";
		et[fasHexPrefix] = "Wrong Hex literal";
		et[fasDecwP] = "Wrong Float literal";
		et[fasFwE] = "Wrong Float with Exponent Literal";
		et[fasFwES] = "Wrong Float with Signed Exponent literal";
		et[fasDotDot] = "Two Dots is not correct lexem";
		et[fas2Q] = "Missing closing double quote";
		et[fas2QandC] = BadStringMsg;
		et[fas1QandC] = CharNotClosedMsg;
		et[fas1Q] = "Missing character";
		et[fas1QandSlash] = CharNotClosedMsg;
		et[fas1QandSimpleTerminal] = CharNotClosedMsg;
		et[fas1QandOct] = CharNotClosedMsg;
		et[fas1QandHex] = CharNotClosedMsg;
		et[fasMLCommentOpen] = MLCommentErrorMsg;
		et[fasMLCommentBody] = MLCommentErrorMsg;
		et[fasMLCommentStar] = MLCommentErrorMsg;
		et[fas2QandSlash] = BadStringMsg;
 	}
}
